/*
 *  Mach-O object file loading
 *
 *  Copyright (c) 2006 Pierre d'Herbemont
 *  Copyright (c) 2010-2011 Tobias Platen
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, see <http://www.gnu.org/licenses/>.
 */
#define _GNU_SOURCE
#include <stdio.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <dlfcn.h>

//this code should be compiled with cocotron gcc,or clang

#import <objc/ObjCModule.h>
#import <objc/objc-class.h>
#import <objc/objc_sel.h>
#import <objc/objc_class.h>
#import "libsystem.h"
#import "preloader.h" 
#include <sys/syscall.h>
#include <mach-o/loader.h>
#include <mach-o/fat.h>
#include <mach-o/nlist.h>
#include <mach-o/reloc.h>
//#include <libkern/OSByteOrder.h>
#include <netinet/in.h>
#include <ringo/debug_logger.h>

#define PTRADD(c,a,b) (c)((int)(a)+(int)(b))





extern void (*_dyld_link_objc)(int dyldversion,int nmess,SEL* sels,int nmods,OBJCModule* mods,int nrefs,Class* refs);

//TODO fixes for new objc runtime

const struct wine_preload_info *wine_main_preload_info = NULL;

//dyld_info_command



void dumpFile(char*filename,void*ptr,int size)
{
    FILE* f=fopen(filename,"w");
    fwrite(ptr,1,size,f);
    fclose(f);
}

void unimplemented()
{	
    printf("inimplemented trap\n");
    exit(-1);
}



typedef struct _vminfo
{
    char* path;
    uint32_t filetype;
    void* vmaddr;
    size_t filesize;
    size_t memsize;
    off_t archoff;
    off_t slide;
    struct _vminfo** dylibs;
    int ndylibs;
    struct segment_command* __IMPORT;
    struct section* __IMPORT__jump_table;
    struct section* __IMPORT__pointers;
    struct segment_command* __OBJC;
    struct section* __OBJC__message_refs;
    struct section* __OBJC__module_info;
    struct section* __OBJC__cls_refs;
    struct section* __DATA__cfstring;
    struct segment_command* __LINKEDIT;
    struct symtab_command* symtab;
    struct dysymtab_command* dysymtab;




} vminfo;


#define TARGET_CPU_TYPE CPU_TYPE_I386
#define TARGET_CPU_NAME "i686"
#define PAGE_SIZE 0x1000


void bswap_fh(struct fat_header *fh)
{
    fh->magic = htonl(fh->magic);
    fh->nfat_arch =  htonl(fh->nfat_arch);

}

void bswap_fa(struct fat_arch *fa)
{
    fa->cputype=htonl(fa->cputype);
    fa->cpusubtype=htonl(fa->cpusubtype);
    fa->offset=htonl(fa->offset);
    fa->size=htonl(fa->size);
    fa->align=htonl(fa->align);
}



/* load a mach-o object file */
vminfo* load_object(const char *filename)
{
    void* dl=dlopen(filename,RTLD_LAZY);
    int need_bswap = 0;
    int entry_point = 0;
    int dyld_command = 0; 
    int slide, mmapfixed;
    int fd;
    struct load_command *lcmds, *lc;
    int is_fat = 0;
    unsigned int i, magic;
    int mach_hdr_pos = 0;
    struct mach_header mach_hdr;
    struct symtab_command *    symtabcmd = 0;

    vminfo* info=malloc(sizeof(vminfo));
    memset(info,0,sizeof(vminfo));
    info->path=filename;
	
    if(dl)
    {
	info->vmaddr=dl;
	info->slide=1;
	return info;
    }	
    
    fd = open(filename, O_RDONLY);
    if (fd < 0)
      //  qerror("can't open file '%s'", filename);
	goto error;
    
    /* Read magic header.  */
    if (read(fd, &magic, sizeof (magic)) != sizeof (magic))
        qerror("unable to read Magic of '%s'", filename);

    /* Check Mach identification.  */
    if(magic == MH_MAGIC)
    {
        is_fat = 0;
        need_bswap = 0;
    } else if (magic == MH_CIGAM)
    {
        is_fat = 0;
        need_bswap = 1;
    } else if (magic == FAT_MAGIC)
    {
        is_fat = 1;
        need_bswap = 0;
    } else if (magic == FAT_CIGAM)
    {
        is_fat = 1;
        need_bswap = 1;
    }
    else
        qerror("Not a Mach-O file.", filename);

    
    if(is_fat)
    {
        int found = 0;
        struct fat_header fh;
        struct fat_arch *fa;

        lseek(fd, 0, SEEK_SET);

        /* Read Fat header.  */
        if (read(fd, &fh, sizeof (fh)) != sizeof (fh))
            qerror("unable to read file header");

        if(need_bswap)
            bswap_fh(&fh);

        /* Read Fat Arch.  */
        fa = malloc(sizeof(struct fat_arch)*fh.nfat_arch);

        if (read(fd, fa, sizeof(struct fat_arch)*fh.nfat_arch) != sizeof(struct fat_arch)*fh.nfat_arch)
            qerror("unable to read file header");

        for( i = 0; i < fh.nfat_arch; i++, fa++)
        {
            //if(need_bswap)
            bswap_fa(fa);
            if(fa->cputype == TARGET_CPU_TYPE)
            {
                mach_hdr_pos = fa->offset;
                lseek(fd, mach_hdr_pos, SEEK_SET);

                info->archoff=fa->offset;
		info->filesize=fa->size;

                /* Read Mach header.  */

                if (read(fd, &mach_hdr, sizeof(struct mach_header)) != sizeof (struct mach_header))
                    qerror("unable to read file header");

                if(mach_hdr.magic == MH_MAGIC)
                    need_bswap = 0;
                else if (mach_hdr.magic == MH_CIGAM)
                    need_bswap = 1;
                else
                    qerror("Invalid mach header in Fat Mach-O File");
                found = 1;
                break;
            }
        }
        if(!found)
            qerror("%s: No %s CPU found in FAT Header", filename, TARGET_CPU_NAME);
    }
    else
    {
        lseek(fd, 0, SEEK_SET);
        /* Read Mach header */
        if (read(fd, &mach_hdr, sizeof (mach_hdr)) != sizeof (mach_hdr))
            qerror("%s: unable to read file header", filename);
	
	

    }

    if ((mach_hdr.cputype) != TARGET_CPU_TYPE)
        qerror("%s: Unsupported CPU 0x%x (only 0x%x(%s) supported)", filename, mach_hdr.cputype, TARGET_CPU_TYPE, TARGET_CPU_NAME);


    switch(mach_hdr.filetype)
    {
    case MH_EXECUTE:
    case MH_DYLIB:
    case MH_BUNDLE:
    case MH_OBJECT:
	break;
    default:
        qerror("%s: Unsupported Mach type (0x%x)", filename, mach_hdr.filetype);
    }

    /* read segment headers */
    lcmds = malloc(mach_hdr.sizeofcmds);

    if(read(fd, lcmds, mach_hdr.sizeofcmds) != mach_hdr.sizeofcmds)
        qerror("%s: unable to read load_command", filename);
    slide = 0;
    mmapfixed = 0;
    //int segaddr;
    char* dumpseg=getenv("DYLD_DUMPSEG");
    int j;
    for(i=0, lc = lcmds; i < (mach_hdr.ncmds) ; i++)
    {

        if(lc->cmd==LC_SEGMENT)
        {
            struct segment_command* scmd=((struct segment_command*)(lc));
            if(strcmp(scmd->segname, "__PAGEZERO") == 0) {}
            else
            {

                if(strcmp(scmd->segname, "__TEXT") == 0)
                {
                    info->vmaddr=(void*)((struct segment_command*)(lc))->vmaddr;
                    info->filetype=mach_hdr.filetype;
                }
                //if(dumpseg && *dumpseg=='1') printf("dyld: seg: %s %x %x\n",scmd->segname,scmd->vmaddr,scmd->vmsize);
                if(strcmp(scmd->segname,"__IMPORT")==0) info->__IMPORT=scmd;
                if(strcmp(scmd->segname,"__OBJC")==0) info->__OBJC=scmd;
                if(strcmp(scmd->segname,"__LINKEDIT")==0) info->__LINKEDIT=scmd;
                struct section* sect=(struct section*)(((int)lc)+sizeof(struct segment_command));
                for(j=0;j<((struct segment_command*)(lc))->nsects;j++)
                {
                    if(dumpseg && *dumpseg=='1') printf("dyld: seg: %s sect: %s addr %x  size: %i reserved1: %x\n",sect[j].segname,
                                                        sect[j].sectname,sect[j].addr,sect[j].size,sect[j].reserved1);
                    if(strcmp(sect[j].sectname,"__jump_table")==0 && strcmp(sect[j].segname,"__IMPORT")==0) info->__IMPORT__jump_table=&sect[j];
                    if(strcmp(sect[j].sectname,"__pointers")==0   && strcmp(sect[j].segname,"__IMPORT")==0) info->__IMPORT__pointers=&sect[j];
                    if(strcmp(sect[j].sectname,"__cls_refs")==0   && strcmp(sect[j].segname,"__OBJC")==0) info->__OBJC__cls_refs=&sect[j];
                    if(strcmp(sect[j].sectname,"__module_info")==0   && strcmp(sect[j].segname,"__OBJC")==0) info->__OBJC__module_info=&sect[j];
                    if(strcmp(sect[j].sectname,"__message_refs")==0   && strcmp(sect[j].segname,"__OBJC")==0) info->__OBJC__message_refs=&sect[j];
		    if(strcmp(sect[j].sectname,"__cfstring")==0   && strcmp(sect[j].segname,"__DATA")==0) info->__DATA__cfstring=&sect[j];




                }


            }


        }
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
    if(info->filesize==0)
    {
	lseek(fd, 0, SEEK_SET);
	info->filesize=lseek(fd, 0, SEEK_END);
    }
    
    



    close(fd);
    return info;
error:
	free(info);
	return nil;
}

void add_dylib(vminfo* parent,vminfo* dylib)
{
   // printf("TODO: add dylib %p %p\n",dylib->vmaddr,dylib->slide);

}

vminfo* load_dylib(struct dylib_command* dylib,vminfo* info)
{
    char* name=(char*)(((int)dylib)+dylib->dylib.name.offset);
    //printf("dyld:loading %s\n",name);
    if(strcmp(name,"/usr/lib/libSystem.B.dylib")==0||strcmp(name,"/usr/lib/libgcc_s.1.dylib")==0||strcmp(name,"/usr/lib/libobjc.A.dylib")==0)
    {
        loadLibSystem();
	return nil;
    }
    else
    {
	    char* last=strrchr(name,'/');
      	    char path[1024];
	    sprintf(path,"lib%s.so",last+1);
	    //printf("try %s %s\n",path,info->path);		
	    vminfo* t=load_object(path);
	    if(t) return t;
    }
#if 0	
	    if(t==NULL)	
	    {
		return;//broken
	    //load native framework
            int idx=last-info->path;
            strcpy(path,info->path);
            path[idx+1]=0;
            strcat(path,name);
            vminfo* t=load_object(path);
	    }
            if (t==NULL)
            {
		fprintf(stderr,"failed to load dylib %s in %s (%s)\n",name,info->path,strrchr(info->path,'/'));
		exit(0);
            }
            info->dylibs[i]=t;
            //TODO add dylib
            //add_dylib(info,t);
	}
    }
#endif
	return NULL;
}


int load_dylibs(vminfo* info)
{
    struct mach_header* mach_hdr=(struct mach_header*)info->vmaddr;
    struct load_command* lc=(struct load_command*)((int)(info->vmaddr)+sizeof(struct mach_header));

    int i;
    int ret=-1;
    int d=0;
    for(i=0 ; i < (mach_hdr->ncmds) ; i++)
    {
        switch(lc->cmd)
	{
        case LC_LOAD_DYLIB:
            d++;
            break;
	}
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
    //printf("ndylibs: %i\n",d);
    info->dylibs=malloc(sizeof(vminfo*)*d);
    info->ndylibs=d;	
    d=0;
    lc=(struct load_command*)((int)(info->vmaddr)+sizeof(struct mach_header));
    for(i=0 ; i < (mach_hdr->ncmds) ; i++)
    {
        switch(lc->cmd)
	{
        case LC_LOAD_DYLIB:
            info->dylibs[d]=load_dylib((struct dylib_command*)lc,info);
            d++;
            ret=0;
            break;
	}
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
    return ret;

}

int load_thread(vminfo* info)
{

    struct mach_header* mach_hdr=(struct mach_header*)info->vmaddr;
    struct load_command* lc=(struct load_command*)(((int)(info->vmaddr))+sizeof(struct mach_header));
    int i;
    for(i=0 ; i < (mach_hdr->ncmds) ; i++)
    {
        switch(lc->cmd)
	{
        case LC_UNIXTHREAD:
            {
		int* th=(int*)((int)lc+sizeof(struct load_command));
		//printf("load thread %x %x %x\n",th[11],th[12],th[10]);
		return th[12];
		
            }
            break;
	}
        lc = (struct load_command*)((int)(lc)+(lc->cmdsize));
    }
    return 0;

}

struct nlist* find_symbol(vminfo* info,char* name)
{
	if(info->slide==1) return nil;
	struct mach_header* mach_hdr=(struct mach_header*)info->slide;
	int i=info->dysymtab->iextdefsym;
	int n=info->dysymtab->nextdefsym;
	struct nlist* nl=(struct nlist*)(((int)mach_hdr)+info->symtab->symoff);
	int j;
	char* strtab=(char*)(((int)mach_hdr)+info->symtab->stroff);
	for(j=0;j<n;j++)
	{
		char* sym=strtab+nl[i+j].n_un.n_strx;
		if(strcmp(sym,name)==0) return &nl[i+j];
	}
	return nil;
}

static int bind_pointer(vminfo* info,char* sym,char* ptr)
{
    //printf("nonlazy: %s: %p\n",sym,ptr);
    void* symaddr=libsystem_dlsym(sym);	
    void** ptr2=(void**) ptr;
    if(symaddr==0 && strcmp(sym,"___stderrp")==0) symaddr=&stderr;//hack
    if(symaddr==0)
    {
        printf("%s undefined\n",sym);
        return 1;
    }
   // printf("addr: %p\n",ptr);
    *ptr2=symaddr;
    return 0;
}

static int bind_jump_table(vminfo* info,char* sym,char* ptr)
{
    //printf("lazy: %s: %p\n",sym,ptr);
    void* symaddr=libsystem_dlsym(sym);
    if(symaddr==0)
    {
	 int i;
         for(i=0;i < info->ndylibs;i++)
    	 {
	     vminfo* dylib=info->dylibs[i];
	     if(dylib)
	     { 
	        struct nlist* n=find_symbol(dylib,sym);
	        if(n)
		{
			 symaddr=n->n_value+dylib->slide;
			break;
		}
	     }	
	 }
	 for(i=0;i < info->ndylibs;i++)
    	 {
	     vminfo* dylib=info->dylibs[i];
	     if(dylib)
	     { 
	        void* n=dlsym(dylib->vmaddr,sym+1);
	        if(n)
		{
			 symaddr=n;
			break;
		}
	     }	
	 }
	
    }	
    if(symaddr==0)
    {
        printf("%s undefined\n",sym);
        return 1;
    }
	//printf("bind: %s\n",sym);
    int rel32=symaddr-((void*)ptr+5);
    ptr[0]=0xE9;//jmp instuction
    ptr[1]=rel32 & 0xFF;
    ptr[2]=(rel32 >>8) & 0xFF;
    ptr[3]=(rel32 >>16) & 0xFF;
    ptr[4]=(rel32 >>24) & 0xFF;

    return 0;
}


static int link_section(vminfo* info,struct section* sect,int size,int (*bindfunc)(vminfo*,char*,char*))
{
    if(sect==0) return 0;//nothing to do

    struct mach_header* mach_hdr=(struct mach_header*)info->vmaddr;
    if(mach_hdr==0) mach_hdr=(struct mach_header*)info->slide; //if mach_hdr ==0 we are a slidable image

    struct nlist* nl=(struct nlist*)(((int)mach_hdr)+info->symtab->symoff);
    char* strtab=(char*)(((int)mach_hdr)+info->symtab->stroff);

    uint32_t*   indirectTable=(uint32_t*)(info->dysymtab->indirectsymoff+(int)mach_hdr);
    uint32_t indirectTableOffset=sect->reserved1;
    int offset=0;
    for(int i=0;i<sect->size/size;i++)
    {
        int index=indirectTable[indirectTableOffset + i];
        char* sym=strtab+nl[index].n_un.n_strx;
        char* ptrToBind=(char*)sect->addr+info->slide;
        ptrToBind+=offset;
        if(bindfunc(info,sym,ptrToBind))return 1;
        offset+=size;
    }
    return 0;
}




//32 bit only
#define LC_SEGMENT_COMMAND LC_SEGMENT
#define macho_segment_command segment_command

int link_file(vminfo* info)
{
    if(getenv("DYLD_NO_LINK")) return 0;
    if(info->slide==1) return 0;//native dylib nothing to do	


	
    struct mach_header* mach_hdr=(struct mach_header*)info->vmaddr;
    		
}

void link_objc(vminfo* info)
{
    //printf("%s linking\n",info->path);	
    if(info->__OBJC)
    {
        char* base=0x0;
	//printf("vmaddr %p size:%i\n",info->__OBJC->vmaddr,info->__OBJC->vmsize);
        mprotect((void*)base+info->__OBJC->vmaddr,info->__OBJC->vmsize,PROT_WRITE|PROT_READ|PROT_EXEC);
        if(info->__OBJC__message_refs && info->__OBJC__module_info && info->__OBJC__cls_refs && _dyld_link_objc)
        {
	    DEBUG_DYLD(printf("link objc\n"));	
            _dyld_link_objc(1,info->__OBJC__message_refs->size/sizeof(SEL),PTRADD(SEL*,base,info->__OBJC__message_refs->addr),
                            info->__OBJC__module_info->size/sizeof(OBJCModule),PTRADD(OBJCModule*,base,info->__OBJC__module_info->addr),
                            info->__OBJC__cls_refs->size/sizeof(Class),PTRADD(Class*,base,info->__OBJC__cls_refs->addr));
        }
        else
        {
            printf("invalid binary or libobjc\n");
            exit(0);
        }
    }
    if(info->__DATA__cfstring)
	{
		//printf("linking cfconstant string class\n");
		extern id (*_objc_getClass)(const char *name);
		//printf("cfstr vmaddr %p size:%i\n",info->__DATA__cfstring->addr,info->__DATA__cfstring->size);
		id constclass=_objc_getClass("NSDarwinString");
		//printf("linking cfconstant string class %p\n",constclass);
		if(constclass)
		{
		//memcpy(info->__DATA__cfstring->addr,constclass,info->__DATA__cfstring->size);
		id* i=info->__DATA__cfstring->addr;
		*i=constclass;
		printf("info->__DATA__cfstring->size=%i\n",info->__DATA__cfstring->size);
		}
		else
		printf("class NSConstantString not found\n");
		//printf("linking cfconstant string class %p end\n",constclass);
	}	
}




//binfmt_misc 
// :osx32:M:0:FEEDFACE::/usr/bin/dyld

int dyld_main(int mh,int argc,char** argv) //is called in start.s
{



    int i;

    if(argv[1]==0)
    {
	printf("usage: dyld program arguments\n");
	exit(1);
    }


    if(wine_main_preload_info==NULL)
    {
        	printf("you will need wine-preloader from the wine package\n");
        	exit(0);
    }
    else
    {
	if(getenv("DYLD_DEBUG"))
	{
            int pid=fork();
            if(pid==0)
            {
                char ppid[32];
                sprintf(ppid,"--pid=%i",getppid());
                execle("/usr/bin/gdb","/usr/bin/gdb",ppid,argv[0],0,environ);
                fprintf(stderr,"faild to start gdb\n");
                exit(0);
            }
            sleep(1);
	}
    }



    
    //if(strcmp(argv[1],"--system")==0) {loadLibSystem();while(1);}
    
    vminfo* t=load_object(argv[1]);


    
    if(load_dylibs(t)) qerror("failed to load dylibs");
    if(link_file(t)) qerror("failed to link\n");
    link_objc(t);
    //add_main_image(t->vmaddr,t->memsize);
    int entry=load_thread(t);
    //shift arguments 
    for(i=0;i<argc;i++)
    {
    	argv[i]=argv[i+1];
    }


    //hack to skip,leopard crt code
    unsigned char* ret=(unsigned char*)entry+49;
    if(*ret==0xE8) return (int)ret;
    printf("dyld: bad runtime\n");
    return entry;		
}
